#include "network/tcp_acceptor.hpp"

#include "base/assert.hpp"
#include "base/log.hpp"
#include "network/tcp_connection.hpp"

#include <cstring>
#include <errno.h>
#include <unistd.h>

#include <sys/un.h>

namespace ibox::network {

TcpAcceptor::TcpAcceptor(event::Loop *wp_loop) : m_loop(wp_loop) {}

TcpAcceptor::~TcpAcceptor() {
    if (m_read_event != nullptr) {
        cleanup();
    }
}

bool TcpAcceptor::initialize(const SockAddr &bind_addr, int listen_backlog) {
    LOG_DEBUG(
        "bind_addr: {}, backlog: {}", bind_addr.to_string(), listen_backlog);

    m_bind_addr = bind_addr;

    SocketFd sock_fd = create_socket(bind_addr.type());
    if (sock_fd.is_null()) {
        LOG_ERROR("create socket fail");
        return false;
    }

    int bind_ret = bind_address(sock_fd, m_bind_addr);
    if (bind_ret < 0) {
        LOG_ERROR("bind address {} fail", m_bind_addr.to_string());
        return false;
    }

    int listen_ret = sock_fd.listen(listen_backlog);
    if (listen_ret < 0) {
        LOG_ERROR("listen fail");
        return false;
    }

    m_listen_fd = std::move(sock_fd);
    m_read_event
        = std::make_unique<event::EpollFdEvent>(m_loop->ptr(),
                                                m_listen_fd.get(),
                                                event::FdEvent::ReadEvent,
                                                event::Event::Mode::Forever);
    m_read_event->set_callback(
        [&](uint32_t events) { on_socket_read(events); });

    return true;
}

SocketFd TcpAcceptor::create_socket(SockAddr::Type addr_type) {
    int flags = SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC;
    if (addr_type == SockAddr::Type::IPv4) {
        return SocketFd::create_socket(AF_INET, flags, 0);
    }
    if (addr_type == SockAddr::Type::Local) {
        return SocketFd::create_socket(AF_LOCAL, flags, 0);
    }
    LOG_ERROR("socket type not support");
    return SocketFd();
}

int TcpAcceptor::bind_address(SocketFd sock_fd, const SockAddr &bind_addr) {
    if (bind_addr.type() == SockAddr::Type::IPv4) {
        sock_fd.set_reuse_address(true);

        struct sockaddr_in sock_addr;
        socklen_t len = bind_addr.to_sockaddr(sock_addr);
        return sock_fd.bind((const struct sockaddr *) &sock_addr, len);
    }

    if (bind_addr.type() == SockAddr::Type::Local) {
        //! 为防止存在的同名文件导致bind失败，在bind之前要先尝试删除原有的文件
        ::unlink(m_bind_addr.to_string().c_str());

        struct sockaddr_un sock_addr;
        socklen_t len = bind_addr.to_sockaddr(sock_addr);
        return sock_fd.bind((const struct sockaddr *) &sock_addr, len);
    }
    LOG_ERROR("bind addr type not support");
    return -1;
}

bool TcpAcceptor::start() {
    if (m_read_event != nullptr) {
        return m_read_event->enable();
    }
    return false;
}

bool TcpAcceptor::stop() {
    if (m_read_event != nullptr) {
        return m_read_event->disable();
    }
    return false;
}

void TcpAcceptor::cleanup() {
    m_listen_fd.close();

    //! 对于Unix Domain的Socket在退出的时候要删除对应的socket文件
    if (m_bind_addr.type() == SockAddr::Type::Local) {
        auto socket_file = m_bind_addr.to_string();
        int ret = ::unlink(socket_file.c_str());
        if (ret != 0) {
            LOG_WARNING("remove file {} fail. errno: {}, {}",
                        socket_file,
                        errno,
                        strerror(errno));
        }
    }
}

void TcpAcceptor::on_socket_read(uint32_t events) {
    if (static_cast<bool>(events & event::FdEvent::ReadEvent)) {
        on_client_connected();
    }
}

void TcpAcceptor::on_client_connected() {
    struct sockaddr addr {};
    socklen_t addr_len{sizeof(addr)};
    Fd accept_fd{m_listen_fd.accept(&addr, &addr_len)};
    SocketFd peer_sock{accept_fd};
    if (peer_sock.is_null()) {
        LOG_WARNING("accept fail");
        return;
    }

    SockAddr peer_addr{addr, addr_len};
    LOG_INFO("{} accepted new connection: {}",
             m_bind_addr.to_string(),
             peer_addr.to_string());

    if (m_new_conn_cb) {
        auto *new_connection = new TcpConnection(m_loop, peer_sock, peer_addr);
        new_connection->enable();
        m_new_conn_cb(new_connection);
    } else {
        LOG_WARNING("{} need connect cb", m_bind_addr.to_string());
    }
}

} // namespace ibox::network
